package jmine.tec.datadigester.positional.meta;

import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import jmine.tec.datadigester.positional.meta.factory.PositionalBigDecimalAnnotationSupport;
import jmine.tec.datadigester.positional.meta.factory.PositionalDateAnnotationSupport;
import jmine.tec.datadigester.positional.meta.factory.PositionalFieldAnnotationSupport;
import jmine.tec.datadigester.positional.meta.factory.PositionalIntAnnotationSupport;
import jmine.tec.datadigester.positional.meta.factory.PositionalLongAnnotationSupport;
import jmine.tec.datadigester.positional.meta.factory.PositionalStringAnnotationSupport;
import jmine.tec.datadigester.positional.meta.factory.PositionalTimestampAnnotationSupport;

import org.apache.commons.beanutils.PropertyUtils;

/**
 * Factory of positional layout metadata, reads from annotations
 * 
 * @author lundberg
 */
public class PositionalLayoutMetadataFactory {

    private Map<Class<? extends Annotation>, PositionalFieldAnnotationSupport<?>> annotationSupports =
            new HashMap<Class<? extends Annotation>, PositionalFieldAnnotationSupport<?>>();

    /**
     * Construtor
     */
    public PositionalLayoutMetadataFactory() {
        this.addAnnotationSupport(new PositionalStringAnnotationSupport());
        this.addAnnotationSupport(new PositionalLongAnnotationSupport());
        this.addAnnotationSupport(new PositionalBigDecimalAnnotationSupport());
        this.addAnnotationSupport(new PositionalTimestampAnnotationSupport());
        this.addAnnotationSupport(new PositionalIntAnnotationSupport());
        this.addAnnotationSupport(new PositionalDateAnnotationSupport());
    }

    /**
     * @param factory factory
     */
    public void addAnnotationSupport(PositionalFieldAnnotationSupport<? extends Annotation> factory) {
        this.annotationSupports.put(factory.annotationClass(), factory);
    }

    /**
     * Creates a positional layout metadata that supports the given of annotated bean
     * 
     * @param beanClass beanClass
     * @return metadata
     */
    public PositionalLineMetadata forBean(Class<?> beanClass) {
        return this.createLineSupport(beanClass);
    }

    /**
     * @param beanClass beanClass
     * @return PositionalLineMetadata PositionalLineMetadata
     */
    private PositionalLineMetadata createLineSupport(Class<?> beanClass) {
        PositionalLineMetadata line = new PositionalLineMetadata(beanClass);

        for (PropertyDescriptor property : PropertyUtils.getPropertyDescriptors(beanClass)) {
            this.supportPropertyMethod(line, property, property.getWriteMethod());
            this.supportPropertyMethod(line, property, property.getReadMethod());
        }

        return line;
    }

    /**
     * Supports a given property method,
     * 
     * @param line line
     * @param property property
     * @param method method
     */
    private void supportPropertyMethod(PositionalLineMetadata line, PropertyDescriptor property, Method method) {
        if (method != null) {
            for (Annotation annotation : method.getAnnotations()) {
                if (this.annotationSupports.containsKey(annotation.annotationType())) {
                    line.addField(this.createFieldSupport(property, this.annotationSupports.get(annotation.annotationType()), annotation));
                }
            }
        }
    }

    /**
     * Creates a field support
     * 
     * @param <A> annotation type
     * @param property property
     * @param support support
     * @param annotation annotation
     * @return PositionalFieldMetadata
     */
    private <A extends Annotation> PositionalFieldMetadata createFieldSupport(PropertyDescriptor property,
            PositionalFieldAnnotationSupport<A> support, Annotation annotation) {
        return support.create(property, support.annotationClass().cast(annotation));
    }

}
